#include <fstream>
#include <sstream>
#include <vector>
#include <cstdlib>
#include "ColorIdGenerator.hpp"
#include "Conversion.hpp"
#include "ObjFileLoader.hpp"
#include "Values.hpp"

ObjFileLoader::ObjFileLoader(void): _isLoaded(false), _nbVertices(0), _vertices(NULL)
{
	_lineParser["v"] = &ObjFileLoader::parseLineVertex;
	_lineParser["vn"] = &ObjFileLoader::parseLineNormal;
	_lineParser["t"] = &ObjFileLoader::parseLineTexture;
	_lineParser["f"] = &ObjFileLoader::parseLineFace;
}


ObjFileLoader::~ObjFileLoader(void)
{
}

unsigned int		ObjFileLoader::getNbVertices() const
{
	return _nbVertices;
}


VertexAttributes*	ObjFileLoader::getVertices(void)
{
	return _vertices;
}

bool		ObjFileLoader::isModelLoaded() const
{
	return _isLoaded;
}

void		ObjFileLoader::load(std::string const& modelFile, const bool calcNormal) throw (std::exception)
{
	std::ifstream	file(modelFile.data());

	if (file)
	{
		readContent(file);
		file.close();
		buildVertexArray(calcNormal);
		_isLoaded = true;
	}
	else
#ifndef __MINGW32__
        throw std::exception((std::string("ObjFileLoader::load: ") + modelFile + ": No such file.").data());
#else
        throw std::exception();
#endif // __MINGW32__
}

void		ObjFileLoader::buildVertexArray(const bool mustCalcNormal)
{
    ColorIdGenerator    generator;
    generator.generate();

	_nbVertices = _readedFaces.size() * 3;

	// Convert list to vector to get faster access to data
	std::vector<Vec3f>*		readedVertex = Conversion::stdListToStdVector<Vec3f>(_readedVertex);
	_readedVertex.clear();
	std::vector<Vec3f>*		readedTexture = Conversion::stdListToStdVector<Vec3f>(_readedTexture);
	_readedTexture.clear();
	std::vector<Vec3f>*		readedNormal = Conversion::stdListToStdVector<Vec3f>(_readedNormal);
	_readedNormal.clear();

	_vertices = new VertexAttributes[_nbVertices];
	std::list<Mat3i>::iterator			itBegin = _readedFaces.begin();
	const std::list<Mat3i>::iterator	itEnd = _readedFaces.end();

	int		itFace = 0, itVertex, offset;
	while (itBegin != itEnd)
	{
		itVertex = 0;
		while (itVertex < 3)
		{
			offset = itFace * 3 + itVertex;
			if (readedVertex != NULL)
			{
				_vertices[offset].posX = (*readedVertex)[(*itBegin)(itVertex, 0) - 1][0];
				_vertices[offset].posY = (*readedVertex)[(*itBegin)(itVertex, 0) - 1][1];
				_vertices[offset].posZ = (*readedVertex)[(*itBegin)(itVertex, 0) - 1][2];
			}
			if (readedTexture != NULL)
			{
				_vertices[offset].textX = (*readedTexture)[(*itBegin)(itVertex, 1) - 1][0];
				_vertices[offset].textY = (*readedTexture)[(*itBegin)(itVertex, 1) - 1][1];
				_vertices[offset].textZ = (*readedTexture)[(*itBegin)(itVertex, 1) - 1][2];
			}

			if (readedNormal != NULL && mustCalcNormal == false)
			{
				_vertices[offset].normX = (*readedNormal)[(*itBegin)(itVertex, 2) - 1][0];
				_vertices[offset].normY = (*readedNormal)[(*itBegin)(itVertex, 2) - 1][1];
				_vertices[offset].normZ = (*readedNormal)[(*itBegin)(itVertex, 2) - 1][2];
			}

            _vertices[offset].colorR = 1.f;
            _vertices[offset].colorG = 1.f;
            _vertices[offset].colorB = 1.f;

            _vertices[offset].idX = generator.getR();
            _vertices[offset].idY = generator.getG();
            _vertices[offset].idZ = generator.getB();

			itVertex++;
		}
		if (mustCalcNormal)
			calcNormal(itFace);
		itBegin++;
		itFace++;
	}

	delete readedVertex;
	delete readedNormal;
	delete readedTexture;

}

void		ObjFileLoader::calcNormal(const int itFace)
{
	Vec3f	v1(_vertices[itFace * 3 + 1].posX - _vertices[itFace * 3].posX,
			   _vertices[itFace * 3 + 1].posY - _vertices[itFace * 3].posY,
			   _vertices[itFace * 3 + 1].posZ - _vertices[itFace * 3].posZ);
	Vec3f	v2(_vertices[itFace * 3 + 2].posX - _vertices[itFace * 3].posX,
			   _vertices[itFace * 3 + 2].posY - _vertices[itFace * 3].posY,
			   _vertices[itFace * 3 + 2].posZ - _vertices[itFace * 3].posZ);
	Vec3f	n;

	n[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
	n[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
	n[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);

	n.normalize();
	for (int itVertex = 0; itVertex < 3; itVertex++)
	{
		_vertices[itFace * 3 + itVertex].normX = n[0];
		_vertices[itFace * 3 + itVertex].normY = n[1];
		_vertices[itFace * 3 + itVertex].normZ = n[2];
	}
}

void		ObjFileLoader::readContent(std::ifstream& file)
{
	char		buffer[BUFFER_SIZE_SMALL + 1] = {0};
	std::string	keyword;
	std::map<std::string, memberPointerLineParser>::iterator	it;


	while (!file.eof())
	{
		file.getline(buffer, BUFFER_SIZE_SMALL);
		keyword = getKeyword(buffer);
		if (keyword != "")
		{
			it = _lineParser.find(keyword);
			if (it != _lineParser.end())
				(this->*(*it).second)(buffer);
		}
	}
}

std::string	ObjFileLoader::getKeyword(std::string line)
{
    unsigned int		spacePos = line.find_first_of(' ');

	if (spacePos != std::string::npos)
	{
		return line.substr(0, spacePos);
	}
	return "";
}

void		ObjFileLoader::parseLineVertex(std::string const& line) throw (std::exception)
{
	Vec3f	value;

	sscanf(line.data(), "v %f %f %f", &(value[0]), &(value[1]), &(value[2]));
	_readedVertex.push_back(value);
}

void		ObjFileLoader::parseLineTexture(std::string const& line) throw (std::exception)
{
	Vec3f	value;

    sscanf(line.data(), "t %f %f", &(value[0]), &(value[1]));
	_readedTexture.push_back(value);
}

void		ObjFileLoader::parseLineNormal(std::string const& line) throw (std::exception)
{
	Vec3f	value;

	sscanf(line.data(), "vn %f %f %f", &(value[0]), &(value[1]), &(value[2]));
	_readedNormal.push_back(value);
}

void		ObjFileLoader::parseLineFace(std::string const& mLine) throw (std::exception)
{
	Mat3i	value;
	std::string	oneValue;
	std::string	line = mLine;

	line = line.substr(line.find_first_of(".-1234567890"));

	// First vertex of the face
	oneValue = line.substr(0, line.find_first_of('/'));
    value(0, 0) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of('/') + 1);

	oneValue = line.substr(0, line.find_first_of('/'));
    value(0, 1) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of('/') + 1);

	oneValue = line.substr(0, line.find_first_of(' '));
    value(0, 2) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of(' ') + 1);

	line = line.substr(line.find_first_of(".-1234567890"));

	// Second vertex of the face
	oneValue = line.substr(0, line.find_first_of('/'));
    value(1, 0) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of('/') + 1);

	oneValue = line.substr(0, line.find_first_of('/'));
    value(1, 1) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of('/') + 1);

	oneValue = line.substr(0, line.find_first_of(' '));
    value(1, 2) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of(' ') + 1);

	line = line.substr(line.find_first_of(".-1234567890"));

	// Third vertex of the face
	oneValue = line.substr(0, line.find_first_of('/'));
    value(2, 0) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of('/') + 1);

	oneValue = line.substr(0, line.find_first_of('/'));
    value(2, 1) = oneValue != "" ? ::atoi(oneValue.data()) : 0;
	line = line.substr(line.find_first_of('/') + 1);

    value(2, 2) = line != "" ? ::atoi(line.data()) : 0;

	_readedFaces.push_back(value);
}

void	ObjFileLoader::saveToFile(std::string const& outFile)
{
	std::ofstream		out(outFile.data());
	std::stringstream	str;

	for (unsigned int c = 0; c < _nbVertices; c++)
	{
		str << "v " <<  _vertices[c].posX << " " << _vertices[c].posY << " " << _vertices[c].posZ << std::endl;
		out.write(str.str().data(), str.str().length());
		str.str("");
	}
	for (unsigned int c = 0; c < _nbVertices; c++)
	{
		str << "n " <<_vertices[c].normX << " " << _vertices[c].normY << " " << _vertices[c].normZ << std::endl;
		out.write(str.str().data(), str.str().length());
		str.str("");
	}
	for (unsigned int c = 0; c < _nbVertices; c += 3)
	{
		str << "f " << c << "//" << c << " " << (c + 1) << "//" << (c + 1) << " " << (c + 2) << "//" << (c + 2) << std::endl;
		out.write(str.str().data(), str.str().length());
		str.str("");
	}
	out.close();
}
